package org.springframework.roo.addon.graph;

import static org.springframework.roo.addon.graph.support.MetaDataFactory.type;
import static org.springframework.roo.classpath.details.MemberFindingUtils.getDeclaredTypeAnnotation;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.beaninfo.BeanInfoMetadataProvider;
import org.springframework.roo.addon.configurable.ConfigurableMetadataProvider;
import org.springframework.roo.addon.graph.visual.Visualizer;
import org.springframework.roo.addon.plural.PluralMetadataProvider;
import org.springframework.roo.classpath.PhysicalTypeDetails;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.itd.AbstractItdMetadataProvider;
import org.springframework.roo.classpath.itd.ItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.operations.ClasspathOperations;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.ProjectMetadata;

@Component( immediate = true )
@Service
public final class GraphMetadataProviderImpl extends
        AbstractItdMetadataProvider implements GraphMetadataProvider
{
    private static final JavaType GRAPH_ENTITY_TYPE = Type.NODE.getGraphAnnotation(false).getAnnotationType();
    private static final JavaType GRAPH_RELATIONSHIP_TYPE = Type.RELATIONSHIP.getGraphAnnotation(false).getAnnotationType();
	@Reference
    private ConfigurableMetadataProvider configurableMetadataProvider;
    @Reference
    private PluralMetadataProvider pluralMetadataProvider;
    @Reference
    private BeanInfoMetadataProvider beanInfoMetadataProvider;
    @Reference
    private ClasspathOperations classpathOperations;
    // FIXME: get the GraphDatabaseProvider from the configuration
    private final GraphDatabaseProvider graphdbProvider = GraphDatabaseProvider.NEO4J;
    private boolean visualize = true;

    protected void activate( ComponentContext context )
    {
        metadataDependencyRegistry.registerDependency(PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType() );
        //metadataDependencyRegistry.registerDependency(ProjectMetadata.getProjectIdentifier(), getProvidesType() );

        registerForTypes(GRAPH_ENTITY_TYPE,GRAPH_RELATIONSHIP_TYPE, GRAPH_ENTITY_TYPE, GRAPH_RELATIONSHIP_TYPE);
    }

	private void registerForTypes(final JavaType...registeredTypes) {
		for (JavaType registeredType : registeredTypes) {
			configurableMetadataProvider.addMetadataTrigger( registeredType );
	        pluralMetadataProvider.addMetadataTrigger( registeredType );
	        beanInfoMetadataProvider.addMetadataTrigger( registeredType );
	        addMetadataTrigger( registeredType );
		}
	}

    protected void deactivate( ComponentContext context )
    {
        unregisterForTypes(GRAPH_ENTITY_TYPE,GRAPH_RELATIONSHIP_TYPE, GRAPH_ENTITY_TYPE, GRAPH_RELATIONSHIP_TYPE);

        //metadataDependencyRegistry.deregisterDependency(ProjectMetadata.getProjectIdentifier(), getProvidesType() );
        metadataDependencyRegistry.deregisterDependency(PhysicalTypeIdentifier.getMetadataIdentiferType(), getProvidesType() );
    }

	private void unregisterForTypes(final JavaType...unregisterTypes) {
		for (JavaType unregisterType : unregisterTypes) {
			configurableMetadataProvider.removeMetadataTrigger( unregisterType );
	        pluralMetadataProvider.removeMetadataTrigger( unregisterType );
	        beanInfoMetadataProvider.removeMetadataTrigger( unregisterType );
	        removeMetadataTrigger(unregisterType);
		}
	}

    @Override
    protected ItdTypeDetailsProvidingMetadataItem getMetadata(
            String metadataIdentificationString, JavaType aspectName,
            PhysicalTypeMetadata governorPhysicalTypeMetadata,
            String itdFilename )
    {
        PhysicalTypeDetails physicalTypeDetails = governorPhysicalTypeMetadata.getMemberHoldingTypeDetails();

        final ClassOrInterfaceTypeDetails governorTypeDetails;
        if ( physicalTypeDetails instanceof ClassOrInterfaceTypeDetails )
        {
            governorTypeDetails = (ClassOrInterfaceTypeDetails) physicalTypeDetails;
        }
        else
        {
            return null;
        }

        final AnnotationMetadata rooGraphEntity = getDeclaredTypeAnnotation(governorTypeDetails, GRAPH_ENTITY_TYPE);
        final AnnotationMetadata rooGraphRelationship = getDeclaredTypeAnnotation(governorTypeDetails, GRAPH_RELATIONSHIP_TYPE);
        final GraphMetadata metadata;

        if ( rooGraphEntity != null )
        {
            metadata = GraphMetadata.node( graphdbProvider,
                    metadataIdentificationString, aspectName,
                    governorPhysicalTypeMetadata, governorTypeDetails,classpathOperations);
        } else
        if ( rooGraphRelationship != null)
        {
            metadata = GraphMetadata.relationship( graphdbProvider,
                    metadataIdentificationString, aspectName,
                    governorPhysicalTypeMetadata, governorTypeDetails,classpathOperations);
        } else {
        	throw new IllegalStateException("not a graph entity or relationship: "+governorPhysicalTypeMetadata.getPhysicalLocationCanonicalPath());
        }
        if ( visualize )
        {
            // FIXME: this cannot be invoked until we have returned from this
            // method
            Visualizer.generateGraph( metadata, itdFilename );
        }
        return metadata;
    }

    @Override
    protected String createLocalIdentifier( JavaType javaType, Path path )
    {
        return GraphMetadata.createIdentifier( javaType, path );
    }

    @Override
    protected String getGovernorPhysicalTypeIdentifier(
            String metadataIdentificationString )
    {
        JavaType javaType = GraphMetadata.getJavaType( metadataIdentificationString );
        Path path = GraphMetadata.getPath( metadataIdentificationString );
        String physicalTypeIdentifier = PhysicalTypeIdentifier.createIdentifier( javaType, path );
        return physicalTypeIdentifier;
    }

    public String getItdUniquenessFilenameSuffix()
    {
        return "GraphEntity";
    }

    public String getProvidesType()
    {
        return GraphMetadata.PROVIDES_TYPE;
    }
}
